package own.stu.loadbalance.base;

import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.ConcurrentLinkedQueue;

import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION;

public class HttpServer implements Closeable {

    protected static Logger logger = LoggerFactory.getLogger(HttpServer.class);

    private final ConcurrentLinkedQueue<Handler> handlers = new ConcurrentLinkedQueue<>();
    private int httpPort;
    private Server server;

    public HttpServer() {
    }

    public HttpServer(int httpPort) {
        this.httpPort = httpPort;
    }

    public void start() throws Exception {
        server = new Server();

        ServerConnector httpConnector = addHttpConnector(server);
        if (httpPort != 0) {
            httpConnector.setPort(httpPort);
        }
        server.setHandler(new QueueHandler());
        server.start();

        httpPort = httpConnector.getLocalPort();
    }

    public static ServerConnector addHttpConnector(Server server) {
        ServerConnector connector = new ServerConnector(server);
        server.addConnector(connector);
        return connector;
    }

    public void enqueue(Handler handler) {
        handlers.offer(handler);
    }

    public String getHttpUrl() {
        return "http://localhost:" + httpPort;
    }

    public Integer getPort() {
        return httpPort;
    }

    public void enqueueOk() {
        enqueueResponse(response -> response.setStatus(200));
    }

    public void enqueueResponse(HttpServletResponseConsumer c) {
        handlers.offer(new ConsumerHandler(c));
    }

    public void enqueueRedirect(int status, String location) {
        enqueueResponse(response -> {
            response.setStatus(status);
            response.setHeader(LOCATION.toString(), location);
        });
    }

    public void reset() {
        handlers.clear();
    }

    @FunctionalInterface
    public interface HttpServletResponseConsumer {

        void apply(HttpServletResponse response) throws IOException, ServletException;
    }

    @Override
    public void close() throws IOException {
        if (server == null) {
            return;
        }
        try {
            if (!server.isStopped())
                server.stop();
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    private static class ConsumerHandler extends AutoFlushHandler {

        private final HttpServletResponseConsumer c;

        ConsumerHandler(HttpServletResponseConsumer c) {
            this(c, false);
        }

        ConsumerHandler(HttpServletResponseConsumer c, boolean closeAfterResponse) {
            super(closeAfterResponse);
            this.c = c;
        }

        @Override
        protected void handle0(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
            c.apply(response);
        }
    }

    public abstract static class AutoFlushHandler extends AbstractHandler {

        private final boolean closeAfterResponse;

        AutoFlushHandler() {
            this(false);
        }

        AutoFlushHandler(boolean closeAfterResponse) {
            this.closeAfterResponse = closeAfterResponse;
        }

        @Override
        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
            handle0(target, baseRequest, request, response);
            response.getOutputStream().flush();
            if (closeAfterResponse) {
                response.getOutputStream().close();
            }
        }

        protected abstract void handle0(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
    }

    private class QueueHandler extends AbstractHandler {

        @Override
        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

            Handler handler = handlers.poll();
            if (handler == null) {
                logger.error("{} No handler enqueued", getHttpUrl());
                response.sendError(500, "No handler enqueued");
                response.getOutputStream().flush();
                response.getOutputStream().close();

            } else {
                handler.handle(target, baseRequest, request, response);
            }
        }
    }
}